import type { RouteLocationNormalized, RouteLocationRaw, Router } from 'vue-router';

import { toRaw, unref } from 'vue';
import { defineStore } from 'pinia';
import { store } from '/@/store';

import { useGo, useRedo } from '/@/hooks/web/usePage';
import { Persistent } from '/@/utils/cache/persistent';

import { PageEnum } from '/@/enums/pageEnum';
import { PAGE_NOT_FOUND_ROUTE, REDIRECT_ROUTE } from '/@/router/routes/basic';
import { getRawRoute } from '/@/utils';
import { MULTIPLE_TABS_KEY } from '/@/enums/cacheEnum';

import projectSetting from '/@/settings/projectSetting';
import { useUserStore } from '/@/store/modules/user';

/**
 * 多标签状态接口
 */
export interface MultipleTabState {
  /**
   * 缓存的标签列表
   */
  cacheTabList: Set<string>;
  /**
   * 已打开的标签列表
   */
  tabList: RouteLocationNormalized[];
  /**
   * 最后拖拽结束的索引
   */
  lastDragEndIndex: number;
}

/**
 * 跳转到指定页面
 * @param router 路由实例
 */
function handleGotoPage(router: Router) {
  // 使用 useGo 自定义钩子获取跳转函数
  const go = useGo(router);
  // 调用跳转函数，传入当前路由的完整路径，并设置 replace 为 true
  go(unref(router.currentRoute).fullPath, true);
}

/**
 * 获取目标路由信息
 * @param tabItem - 选项卡项的路由信息
 * @returns 目标路由信息对象，包含参数、路径和查询参数
 */
const getToTarget = (tabItem: RouteLocationNormalized) => {
  const { params, path, query } = tabItem;
  return {
    params: params || {},
    path,
    query: query || {},
  };
};

//是否缓存标签页
const cacheTab = projectSetting.multiTabsSetting.cache;

export const useMultipleTabStore = defineStore({
  id: 'app-multiple-tab',
  state: (): MultipleTabState => ({
    // 缓存的标签列表
    cacheTabList: new Set(),
    // 打开的标签列表
    tabList: cacheTab ? Persistent.getLocal(MULTIPLE_TABS_KEY) || [] : [],
    // Index of the last moved tab
    lastDragEndIndex: 0,
  }),
  getters: {
    getTabList(state): RouteLocationNormalized[] {
      return state.tabList;
    },
    getCachedTabList(state): string[] {
      return Array.from(state.cacheTabList);
    },
    getLastDragEndIndex(state): number {
      return state.lastDragEndIndex;
    },
  },
  actions: {
    /**
     * 异步更新缓存的标签页列表
     */
    async updateCacheTab() {
      // 创建一个新的 Set 集合用于存储缓存的标签页名称
      const cacheMap: Set<string> = new Set();

      // 遍历当前已有的标签页列表
      for (const tab of this.tabList) {
        // 获取每个标签页路由的原始信息
        const item = getRawRoute(tab);
        // 检查当前标签页是否被标记为需要缓存，即未被设置为忽略 keep-alive
        const needCache = !item.meta?.ignoreKeepAlive;
        // 如果当前标签不需要缓存，则继续下一次循环
        if (!needCache) {
          continue;
        }
        // 获取当前标签页的名称，并确保其类型为 string
        const name = item.name as string;
        // 将该名称添加到缓存映射集合中
        cacheMap.add(name);
      }
      // 更新组件实例的缓存标签列表为新的缓存映射集合
      this.cacheTabList = cacheMap;
    },

    /**
     * 刷新页面并删除缓存的选项卡。
     * @param router - 路由实例。
     * @returns 一个 Promise，表示刷新页面的异步操作。
     */
    async refreshPage(router: Router) {
      const { currentRoute } = router;
      const route = unref(currentRoute);
      const name = route.name;

      const findTab = this.getCachedTabList.find((item) => item === name);
      if (findTab) {
        this.cacheTabList.delete(findTab);
      }
      const redo = useRedo(router);
      await redo();
    },

    clearCacheTabs(): void {
      this.cacheTabList = new Set();
    },
    resetState(): void {
      this.tabList = [];
      this.clearCacheTabs();
    },

    /**
     * 跳转到指定页面并替换当前页面历史记录
     * @param router - 路由实例
     */
    goToPage(router: Router) {
      const go = useGo(router);
      const len = this.tabList.length;
      const { path } = unref(router.currentRoute);

      // 初始化目标路径为默认首页路径
      let toPath: PageEnum | string = PageEnum.BASE_HOME;

      //获取最后一个标签页的路径，并将其赋值给 toPath 变量作为目标路径
      if (len > 0) {
        const page = this.tabList[len - 1];
        const p = page.fullPath || page.path;
        if (p) {
          toPath = p;
        }
      }
      // 如果当前路径与目标路径不相等，则跳转到目标路径，并替换当前页面历史记录
      path !== toPath && go(toPath as PageEnum, true);
    },

    /**
     * 添加标签页
     * @param {RouteLocationNormalized} route - 需要添加的路由对象
     * @description
     * 此方法用于将新的路由添加到标签列表中。
     * 如果当前路径是错误页面或登录页面，或者路由名称不存在，或者路由名称为重定向或404页面，则不添加标签。
     * 如果标签列表中已存在该路由，则更新该标签页的参数和查询字符串。
     * 如果标签列表中不存在该路由，并且当前路由的动态等级不为负数且超过设定的动态等级，
     * 则先移除对应真实路径的旧标签页再添加新标签页。
     * 最后，更新缓存中的标签页列表。
     */
    async addTab(route: RouteLocationNormalized) {
      const { path, name, fullPath, params, query, meta } = getRawRoute(route);
      // 404  The page does not need to add a tab
      if (
        path === PageEnum.ERROR_PAGE ||
        path === PageEnum.BASE_LOGIN ||
        !name ||
        [REDIRECT_ROUTE.name, PAGE_NOT_FOUND_ROUTE.name].includes(name as string)
      ) {
        return;
      }

      let updateIndex = -1;
      // Existing pages, do not add tabs repeatedly
      const tabHasExists = this.tabList.some((tab, index) => {
        updateIndex = index;
        return (tab.fullPath || tab.path) === (fullPath || path);
      });

      // If the tab already exists, perform the update operation
      if (tabHasExists) {
        const curTab = toRaw(this.tabList)[updateIndex];
        if (!curTab) {
          return;
        }
        curTab.params = params || curTab.params;
        curTab.query = query || curTab.query;
        curTab.fullPath = fullPath || curTab.fullPath;
        //用来删除 tabList 数组中的一个元素，并在相同位置插入新的元素 curTab
        this.tabList.splice(updateIndex, 1, curTab);
      } else {
        // Add tab
        // 获取动态路由打开数，超过 0 即代表需要控制打开数
        const dynamicLevel = meta?.dynamicLevel ?? -1;
        if (dynamicLevel > 0) {
          // 如果动态路由数大于 0 了，那么就要限制该路由的打开数限制了
          // 首先获取到真实的路由，使用配置方式减少计算开销.
          // const realName: string = path.match(/(\S*)\//)![1];
          const realPath = meta?.realPath ?? '';
          // 检查是否已经存在足够数量的相同 `realPath` 的标签页面
          if (
            this.tabList.filter((e) => e.meta?.realPath ?? '' === realPath).length >= dynamicLevel
          ) {
            // 找到具有相同 `realPath` 的标签页的索引
            const index = this.tabList.findIndex((item) => item.meta.realPath === realPath);
            // 如果找到了，就删除这个标签页，确保标签页数量不会超出 `dynamicLevel` 的限制
            index !== -1 && this.tabList.splice(index, 1);
          }
        }
        this.tabList.push(route);
      }
      this.updateCacheTab();
      cacheTab && Persistent.setLocal(MULTIPLE_TABS_KEY, this.tabList);
    },

    /**
     * 异步关闭标签页
     * @param tab 需要关闭的标签页对象，类型为RouteLocationNormalized
     * @param router Vue Router实例，用来执行路由跳转等操作
     */
    async closeTab(tab: RouteLocationNormalized, router: Router) {
      // 辅助函数用于关闭非固定(tab未被固定)的标签页
      const close = (route: RouteLocationNormalized) => {
        // 解构出完整路径和元信息对象中的固定affix属性
        const { fullPath, meta: { affix } = {} } = route;
        if (affix) {
          // 如果是affix标签，则不执行关闭操作
          return;
        }
        // 在标签列表中找到该路由的索引位置
        const index = this.tabList.findIndex((item) => item.fullPath === fullPath);
        // 如果找到，就从tabList中移除该项
        index !== -1 && this.tabList.splice(index, 1);
      };

      const { currentRoute, replace } = router;

      // 获取当前活动路由的路径
      //unref 的作用就是自动地取出响应式对象的内部值。如果你传递给 unref 的是一个 ref，它将返回 .value 属性；如果传递的不是 ref，它将直接返回该值，不做任何改变。
      //使用 unref 可以确保无论 currentRoute 是否为 ref 类型，你都能得到其内部的 path 值。这样做的一个好处是能写出更加通用的代码，因为你不需要知道 currentRoute 的类型。
      const { path } = unref(currentRoute);
      if (path !== tab.path) {
        // 如果要关闭的标签和当前激活的标签不一致，则直接关闭该标签
        close(tab);
        // 更新缓存的标签列表
        this.updateCacheTab();
        return;
      }

      // 如果要关闭的标签是当前激活的标签，则需要找到一个新的目标标签来跳转
      let toTarget: RouteLocationRaw = {};

      // 查找当前激活标签在标签列表中的索引位置
      const index = this.tabList.findIndex((item) => item.path === path);

      // 如果激活的标签是列表中最左边的标签
      if (index === 0) {
        // 如果这是唯一的标签，则跳转到首页
        if (this.tabList.length === 1) {
          const userStore = useUserStore();
          toTarget = userStore.getUserInfo.homePath || PageEnum.BASE_HOME;
        } else {
          // 否则跳转到右边的标签
          const page = this.tabList[index + 1];
          toTarget = getToTarget(page);
        }
      } else {
        // 如果激活的标签不是列表中最左边的标签，则跳转到左边的标签
        const page = this.tabList[index - 1];
        toTarget = getToTarget(page);
      }

      // 关闭当前活动的标签
      close(currentRoute.value);
      // 执行路由替换操作，跳转到目标标签
      await replace(toTarget);
    },

    // Close according to key
    async closeTabByKey(key: string, router: Router) {
      const index = this.tabList.findIndex((item) => (item.fullPath || item.path) === key);
      if (index !== -1) {
        await this.closeTab(this.tabList[index], router);
        const { currentRoute, replace } = router;
        // 检查当前路由是否存在于tabList中
        const isActivated = this.tabList.findIndex((item) => {
          return item.fullPath === currentRoute.value.fullPath;
        });
        // 如果当前路由不存在于TabList中，尝试切换到其它路由
        if (isActivated === -1) {
          let pageIndex;
          if (index > 0) {
            pageIndex = index - 1;
          } else if (index < this.tabList.length - 1) {
            pageIndex = index + 1;
          } else {
            pageIndex = -1;
          }
          if (pageIndex >= 0) {
            const page = this.tabList[index - 1];
            const toTarget = getToTarget(page);
            await replace(toTarget);
          }
        }
      }
    },

    // Sort the tabs
    async sortTabs(oldIndex: number, newIndex: number) {
      const currentTab = this.tabList[oldIndex];
      this.tabList.splice(oldIndex, 1);
      this.tabList.splice(newIndex, 0, currentTab);
      this.lastDragEndIndex = this.lastDragEndIndex + 1;
    },

    // Close the tab on the right and jump
    async closeLeftTabs(route: RouteLocationNormalized, router: Router) {
      const index = this.tabList.findIndex((item) => item.path === route.path);

      if (index > 0) {
        const leftTabs = this.tabList.slice(0, index);
        const pathList: string[] = [];
        for (const item of leftTabs) {
          const affix = item?.meta?.affix ?? false;
          if (!affix) {
            pathList.push(item.fullPath);
          }
        }
        this.bulkCloseTabs(pathList);
      }
      this.updateCacheTab();
      handleGotoPage(router);
    },

    // Close the tab on the left and jump
    async closeRightTabs(route: RouteLocationNormalized, router: Router) {
      const index = this.tabList.findIndex((item) => item.fullPath === route.fullPath);

      if (index >= 0 && index < this.tabList.length - 1) {
        const rightTabs = this.tabList.slice(index + 1, this.tabList.length);

        const pathList: string[] = [];
        for (const item of rightTabs) {
          const affix = item?.meta?.affix ?? false;
          if (!affix) {
            pathList.push(item.fullPath);
          }
        }
        this.bulkCloseTabs(pathList);
      }
      this.updateCacheTab();
      handleGotoPage(router);
    },

    async closeAllTab(router: Router) {
      this.tabList = this.tabList.filter((item) => item?.meta?.affix ?? false);
      this.clearCacheTabs();
      this.goToPage(router);
    },

    /**
     * Close other tabs
     */
    async closeOtherTabs(route: RouteLocationNormalized, router: Router) {
      const closePathList = this.tabList.map((item) => item.fullPath);

      const pathList: string[] = [];

      for (const path of closePathList) {
        if (path !== route.fullPath) {
          const closeItem = this.tabList.find((item) => item.fullPath === path);
          if (!closeItem) {
            continue;
          }
          const affix = closeItem?.meta?.affix ?? false;
          if (!affix) {
            pathList.push(closeItem.fullPath);
          }
        }
      }
      this.bulkCloseTabs(pathList);
      this.updateCacheTab();
      Persistent.setLocal(MULTIPLE_TABS_KEY, this.tabList, true);
      handleGotoPage(router);
    },

    /**
     * 批量关闭标签页
     * @param pathList 需要关闭的标签页路径数组
     */
    async bulkCloseTabs(pathList: string[]) {
      // 过滤掉 tabList 中 fullPath 在 pathList 中的标签项
      this.tabList = this.tabList.filter((item) => !pathList.includes(item.fullPath));
    },

    /**
     * Set tab's title
     */
    async setTabTitle(title: string, route: RouteLocationNormalized) {
      const findTab = this.getTabList.find((item) => item === route);
      if (findTab) {
        findTab.meta.title = title;
        await this.updateCacheTab();
      }
    },
    /**
     * replace tab's path
     * **/
    async updateTabPath(fullPath: string, route: RouteLocationNormalized) {
      const findTab = this.getTabList.find((item) => item === route);
      if (findTab) {
        findTab.fullPath = fullPath;
        findTab.path = fullPath;
        await this.updateCacheTab();
      }
    },
  },
});

// Need to be used outside the setup
export function useMultipleTabWithOutStore() {
  return useMultipleTabStore(store);
}
